// transform from https://github.com/webpack/webpack-dev-server/blob/master/bin/webpack-dev-server.js

import * as net from "net";
import * as fs from "fs";
import * as portfinder from "portfinder";

const webpack = require("webpack");
const addDevServerEntrypoints = require("webpack-dev-server/lib/util/addDevServerEntrypoints");
const Server = require("webpack-dev-server/lib/Server");
const OptionsValidationError = require("webpack-dev-server/lib/OptionsValidationError");
const createDomain = require("webpack-dev-server/lib/util/createDomain");
const open = require("opn");

function versionInfo() {
  return (
    `webpack-dev-server ${require("../package.json").version}\n` +
    `webpack ${require("webpack/package.json").version}`
  );
}

function colorInfo(useColor: boolean, msg: string) {
  if (useColor) {
    // Make text blue and bold, so it *pops*
    return `\u001b[1m\u001b[34m${msg}\u001b[39m\u001b[22m`;
  }
  return msg;
}

function colorError(useColor: boolean, msg: string) {
  if (useColor) {
    // Make text red and bold, so it *pops*
    return `\u001b[1m\u001b[31m${msg}\u001b[39m\u001b[22m`;
  }
  return msg;
}

function startDevServer(webpackOptions: any, options: any) {
  addDevServerEntrypoints(webpackOptions, options);

  let compiler;
  try {
    compiler = webpack(webpackOptions);
  } catch (e) {
    if (e instanceof webpack.WebpackOptionsValidationError) {
      console.error(colorError(options.stats.colors, e.message));
      process.exit(1); // eslint-disable-line
    }
    throw e;
  }

  const suffix = options.inline !== false || options.lazy === true ? "/" : "/webpack-dev-server/";

  let server: any;
  try {
    server = new Server(compiler, options);
  } catch (e) {
    if (e instanceof OptionsValidationError) {
      console.error(colorError(options.stats.colors, e.message));
      process.exit(1); // eslint-disable-line
    }
    throw e;
  }

  ["SIGINT", "SIGTERM"].forEach((sig: any) => {
    process.on(sig, () => {
      server.close(() => {
        process.exit(); // eslint-disable-line no-process-exit
      });
    });
  });

  if (options.socket) {
    server.listeningApp.on("error", (e: any) => {
      if (e.code === "EADDRINUSE") {
        const clientSocket = new net.Socket();
        clientSocket.on("error", (clientError: any) => {
          if (clientError.code === "ECONNREFUSED") {
            // No other server listening on this socket so it can be safely removed
            fs.unlinkSync(options.socket);
            server.listen(options.socket, options.host, (err: any) => {
              if (err) throw err;
            });
          }
        });
        clientSocket.connect({ path: options.socket }, () => {
          throw new Error("This socket is already used");
        });
      }
    });
    server.listen(options.socket, options.host, (err: any) => {
      if (err) throw err;
      // chmod 666 (rw rw rw)
      const READ_WRITE = 438;
      fs.chmod(options.socket, READ_WRITE, fsError => {
        if (fsError) throw fsError;

        const uri = createDomain(options, server.listeningApp) + suffix;
        reportReadiness(uri, options);
      });
    });
  } else {
    server.listen(options.port, options.host, (err: any) => {
      if (err) throw err;

      const uri = createDomain(options, server.listeningApp) + suffix;
      reportReadiness(uri, options);
    });
  }
}

function reportReadiness(uri: string, options: any) {
  const useColor = true;
  const contentBase = Array.isArray(options.contentBase)
    ? options.contentBase.join(", ")
    : options.contentBase;

  if (!options.quiet) {
    let startSentence = `Project is running at ${colorInfo(useColor, uri)}`;
    if (options.socket) {
      startSentence = `Listening to socket at ${colorInfo(useColor, options.socket)}`;
    }

    console.log(`webpack output is served from ${colorInfo(useColor, options.publicPath)}`);

    if (contentBase) {
      console.log(`Content not from webpack is served from ${colorInfo(useColor, contentBase)}`);
    }

    if (options.historyApiFallback) {
      console.log(
        `404s will fallback to ${colorInfo(
          useColor,
          options.historyApiFallback.index || "/index.html"
        )}`
      );
    }
  }

  if (options.open) {
    let openOptions = {} as any;
    let openMessage = "Unable to open browser";

    if (typeof options.open === "string") {
      openOptions = { app: options.open };
      openMessage += `: ${options.open}`;
    }

    open(uri + (options.openPage || ""), openOptions).catch(() => {
      console.log(
        `${openMessage}. If you are running in a headless environment, please do not use the open flag.`
      );
    });
  }
}

export const start = (webpackOptions: any) => {
  const firstWpOpt = Array.isArray(webpackOptions) ? webpackOptions[0] : webpackOptions;
  const options = webpackOptions.devServer || firstWpOpt.devServer || {};

  options.host = "localhost";
  options.hot = false;
  options.inline = true;
  options.quiet = false;
  options.open = true;

  if (!options.filename) {
    options.filename = firstWpOpt.output && firstWpOpt.output.filename;
  }

  if (!options.watchOptions) {
    options.watchOptions = firstWpOpt.watchOptions;
  }

  if (!options.stats) {
    options.stats = {
      cached: false,
      cachedAssets: false
    };
  }

  if (options.open && !options.openPage) {
    options.openPage = "";
  }

  if (!options.publicPath) {
    // eslint-disable-next-line
    options.publicPath = (firstWpOpt.output && firstWpOpt.output.publicPath) || "";
    if (!/^(https?:)?\/\//.test(options.publicPath) && options.publicPath[0] !== "/") {
      options.publicPath = `/${options.publicPath}`;
    }
  }

  options.stats.colors = require("supports-color");

  (portfinder as any).basePort = 8080;
  portfinder.getPort((err, port) => {
    if (err) throw err;
    options.port = port;
    startDevServer(webpackOptions, options);
  });
};
